package com.iteaj.iot.client.protocol;

import com.iteaj.iot.client.SocketClient;
import com.iteaj.iot.*;
import com.iteaj.iot.business.BusinessFactory;
import com.iteaj.iot.client.ClientComponent;
import com.iteaj.iot.client.ClientMessage;
import com.iteaj.iot.config.ConnectProperties;
import com.iteaj.iot.consts.ExecStatus;
import io.netty.channel.ChannelFuture;
import org.springframework.util.StringUtils;

import java.nio.channels.ClosedChannelException;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * 首先它是一个客户端协议
 * 其次它是一个客户端主动请求服务端的协议
 * @param <C>
 */
public abstract class ClientInitiativeProtocol<C extends ClientMessage> extends ClientSocketProtocol<C> implements ProtocolPreservable {

    private long timeout = 10 * 1000; // 超时时间, 默认10秒
    private CountDownLatch downLatch; //同步锁

    // 自定义协议处理器
    private FreeProtocolHandle freeProtocolHandle;
    /** 协议回调状态 */
    private ExecStatus execStatus = ExecStatus.success;

    /**
     * 1. 如果业务处理器存在则执行业务
     * 2. 如果是同步请求则交由业务线程处理
     * 3. 如果是异步请求则交由netty工作线程处理 这种情况最好不要执行耗时的动作
     * @param factory 协议业务
     * @return
     */
    @Override
    public AbstractProtocol exec(BusinessFactory factory) {
        ProtocolHandle protocolHandle = getProtocolHandle();

        // 如果是同步请求则交由其线程执行业务
        if(protocolHandle != null && !isSyncRequest()) {
            return this.exec(protocolHandle);
        }

        return null;
    }

    @Override
    public AbstractProtocol exec(ProtocolHandle handle) {
        if(handle != null) {
            handle.handle(this);
        } else {
            throw new ProtocolException("参数[handle]不能为null");
        }
        // 客户端主动调用的协议不需要在响应给服务端, 返回null
        return null;
    }

    @Override
    public ClientSocketProtocol buildRequestMessage() {
        this.requestMessage = doBuildRequestMessage();

        if(this.requestMessage == null) {
            throw new ProtocolException("构建请求报文失败");
        }

        if(this.requestMessage().getHead() == null || this.requestMessage().getBody() == null) {
            throw new ProtocolException("错误的请求报文, 没有设置[MessageHead] or [MessageBody]");
        }

        return this;
    }

    protected abstract C doBuildRequestMessage();

    /**
     *
     * @return
     */
    @Override
    public ClientSocketProtocol<C> buildResponseMessage() {
        // 只对成功的报文进行报文构建
        if(this.getExecStatus() == ExecStatus.success) {
            doBuildResponseMessage(responseMessage());
        }

        return null;
    }

    public final AbstractProtocol buildResponseMessage(C responseMessage) {
        try {
            this.responseMessage = responseMessage;
            return this.buildResponseMessage();
        } finally {
            // 如果是同步请求必须释放锁
            this.releaseLock();
        }
    }

    /**
     * @param responseMessage
     */
    public abstract void doBuildResponseMessage(C responseMessage);

    /**
     * 平台主动向外发起请求
     * @return 只有在同步的时候{@link #sync(long)}会返回对象, 如果是异步返回 Null
     */
    protected <T extends ClientInitiativeProtocol> T sendRequest() throws ProtocolException {
        try {
            // 构建报文
            buildRequestMessage();

            SocketClient client = getIotClient();
            if(null == client) {
                throw new ProtocolException("获取客户端失败");
            }

            if(!client.getChannel().isActive()) {
                throw new ProtocolException("请求异常 - 已和远程主机断开连接["+client.getConfig()+"]");
            }

            ChannelFuture request = client.writeAndFlush(this);

            if(request != null) {
                if(getTimeout() > 0) {
                    request.get(getTimeout(), TimeUnit.SECONDS); // 此处用来等待报文发送成功
                }
            } else {
                throw new ProtocolException("请求失败");
            }

            /**
             * 是同步请求
             */
            if(isSyncRequest()) {

                boolean await = getDownLatch().await(getTimeout(), TimeUnit.SECONDS);// 如果发送成功等待报文响应

                if(!await) {
                    setExecStatus(ExecStatus.timeout);
                }

                // 同步执行业务
                ProtocolHandle protocolHandle = getProtocolHandle();
                if(protocolHandle != null) {
                    protocolHandle.handle(this);
                }

                return (T) this;
            }

        } catch (InterruptedException e) {
            throw new ProtocolException("同步中断", e);
        } catch (ExecutionException e) {
            if(e.getCause() instanceof ClosedChannelException) {
                throw new ProtocolException("连接异常, 通道关闭", e.getCause());
            } else {
                throw new ProtocolException("请求失败", e);
            }
        } catch (TimeoutException e) {
            throw new ProtocolException("网络异常, 请求超时", e);
        }

        return null; // 只有同步请求的时候才有返回值
    }

    /**
     * 平台主动发起请求
     * @param handle 需要处理的业务
     * @return 只有在同步的时候{@link #sync(long)}会返回对象, 如果是异步返回 Null
     */
    public <T extends ClientInitiativeProtocol> T request(FreeProtocolHandle<T> handle) throws ProtocolException{
        this.freeProtocolHandle = handle;
        if(this.getFreeProtocolHandle() == null) {
            throw new ProtocolException("[handle]不能为Null");
        }

        // 指定要执行的业务后, 必须指定超时时间
        validateTimeout(getTimeout());

        return (T) this.request();
    }

    /**
     * 请求默认的远程主机 {@link ClientComponent#getClient()}
     * @return
     * @throws ProtocolException
     */
    public ClientInitiativeProtocol request() throws ProtocolException {
        return this.sendRequest();
    }

    /**
     *  请求指定的远程主机, 如果主机不存在则会先创建{@link #getIotClient()}
     * @param host 远程主机
     * @param port 远程端口
     * @return
     */
    public ClientInitiativeProtocol request(String host, int port) {
        if(!StringUtils.hasText(host) || port<0) {
            throw new IllegalArgumentException("未指定请求服务器的[host or port]");
        }

        this.setClientKey(new ConnectProperties(host, port));
        return request();
    }

    /**
     *  请求指定的远程主机, 如果主机不存在则会先创建{@link #getIotClient()}
     * @param server 远程主机
     * @return
     */
    public ClientInitiativeProtocol request(ConnectProperties server) {
        if(server == null) {
            throw new IllegalArgumentException("未指定请求服务器的参数[server]");
        }

        this.setClientKey(server);
        return request();
    }

    /**
     *  请求指定的远程主机, 如果主机不存在则会先创建{@link #getIotClient()}
     * @param server 远程主机
     * @param handle 自定义处理器
     * @return
     */
    public <T extends ClientInitiativeProtocol> T request(ConnectProperties server, FreeProtocolHandle<T> handle) {
        if(server == null) {
            throw new IllegalArgumentException("未指定请求服务器的参数[server]");
        }
        this.setClientKey(server);
        return request(handle);
    }

    protected ProtocolHandle getProtocolHandle() {
        ProtocolHandle protocolHandle = getFreeProtocolHandle();
        if(protocolHandle == null) {
            protocolHandle = getDefaultProtocolHandle();
        }

        return protocolHandle;
    }

    protected void validateTimeout(long timeout) {
        if(timeout < 0) {
            throw new ProtocolException("超时时间(timeout)必须大于0(s)");
        }

        this.timeout = timeout;
    }

    /**
     * 此客户端是否激活
     * @return
     */
    public boolean isActive() {
        return getIotClient().getChannel().isActive();
    }

    /**
     * 先和服务端尝试链接, 如果需要的话
     * @return
     */
    public Optional<ClientSocketProtocol> activeIfNeed() {
        // 如果已经激活, 直接发送数据
        if (!this.isActive()) {
            // 没有激活, 先尝试激活
            try {
                getIotClient().doConnect(10);
            } catch (ProtocolException e) {
                return Optional.ofNullable(null);
            }

        }
        return Optional.of(this);
    }

    /**
     * 同步请求
     * @param timeout 同步超时时间
     * @return
     */
    public ClientInitiativeProtocol<C> sync(long timeout) {
        validateTimeout(timeout);
        setDownLatch(new CountDownLatch(1));
        return this;
    }

    /**
     * 设置异步超时时间
     * @see com.iteaj.iot.AbstractProtocolTimeoutManager 超时报文管理
     * @param timeout 超时时间
     * @return
     */
    public ClientInitiativeProtocol<C> timeout(long timeout) {
        validateTimeout(timeout);
        return this;
    }

    protected String getMessageId() {
        ClientMessage message = requestMessage();
        return message.getMessageId();
    }

    @Override
    public abstract ProtocolType protocolType();

    public ExecStatus getExecStatus() {
        return execStatus;
    }

    public void setExecStatus(ExecStatus execStatus) {
        this.execStatus = execStatus;
    }

    /**
     * 是否进行同步请求处理,默认否
     * @return
     */
    public boolean isSyncRequest(){
        return getDownLatch() != null;
    }

    public void releaseLock() {
        if(isSyncRequest()) {
            getDownLatch().countDown();
        }
    }

    protected CountDownLatch getDownLatch() {
        return downLatch;
    }

    protected void setDownLatch(CountDownLatch downLatch) {
        this.downLatch = downLatch;
    }

    @Override
    public String relationKey() {
        return getMessageId();
    }

    public long getTimeout() {
        return timeout;
    }

    public FreeProtocolHandle getFreeProtocolHandle() {
        return freeProtocolHandle;
    }
}
